import json
from flask import Blueprint, request, jsonify
from utils import Neo4jUtil
from components import resDTO, errDTO

graphBlueprint = Blueprint('graph', __name__, url_prefix='/graph')

# 初始化连接
neo4jDB = Neo4jUtil()


def add_node(current_node, path, relations, types):
    for node_id, relation, node_type in zip(path, relations, types):
        # Check if the node already exists in the current level
        existing_node = next((child for child in current_node["children"] if child["id"] == node_id), None)
        if existing_node:
            # Node already exists, move to the next level
            current_node = existing_node
        else:
            # Node doesn't exist, add it to the current level with relation and type
            new_node = {"id": node_id, "relation": relation, "type": node_type, "children": []}
            current_node.setdefault("children", []).append(new_node)
            # Move to the next level
            current_node = new_node


# 将查询的结果重新封装
def searchResToDict(searchResult):
    if searchResult is None or len(searchResult) == 0:
        return None
    res = list()
    for item in searchResult:
        rel = item['rel']
        entity1 = rel.start_node
        entity1_name = entity1['name']
        entity1_label = str(entity1.labels).strip(":")
        entity2 = rel.end_node
        entity2_name = entity2['name']
        entity2_label = str(entity2.labels).strip(":")
        relation = list(rel.types())[0]
        item_dict = {
            "source_node": entity1_name,
            "source_type": entity1_label,
            "target_node": entity2_name,
            "target_type": entity2_label,
            "relation": relation
        }
        res.append(item_dict)
    return res


# 路径转换为树
def searchResPathToDict(searchResult):
    if searchResult is None or len(searchResult) == 0:
        return None
    node_map = {"id": searchResult[0]['path'].nodes[0]['name'],
                "type": str(searchResult[0]['path'].nodes[0].labels).strip(":"), "children": []}
    for path in searchResult:
        path = path['path']
        nodes = [node['name'] for node in path.nodes]
        rels = [list(rel.types())[0] for rel in path.relationships]
        node_types = [str(node.labels).strip(":") for node in path.nodes]
        add_node(node_map, nodes[1:], rels, node_types[1:])
    return node_map


# 路径转为图
def searchResPathToGraph(searchResult):
    if searchResult is None or len(searchResult) == 0:
        return None
    nodesRes = list()
    edgesRes = list()
    nodeSet = set()
    edgeSet = set()
    for path in searchResult:
        path = path['path']
        nodes = [node['name'] for node in path.nodes]
        rels = [list(rel.types())[0] for rel in path.relationships]
        node_types = [str(node.labels).strip(":") for node in path.nodes]
        for i in range(len(nodes)):
            nodeSet.add((nodes[i], node_types[i]))
        for i in range(len(rels)):
            edgeSet.add((nodes[i], rels[i], nodes[i + 1]))
    for node in nodeSet:
        nodesRes.append({
            "id": node[0],
            "label": node[0],
            "cluster": node[1]
        })
    for edge in edgeSet:
        edgesRes.append({
            "source": edge[0],
            "target": edge[2],
            'relation': edge[1]
        })

    return nodesRes, edgesRes


# 根据实体名称进行模糊查询, 返回一个列表
@graphBlueprint.post("/queryByKeyword")
def queryByKeyword():
    """
    实体模糊查询接口
    ---
    tags:
      - 图查询接口
    description:
        图查询接口，json格式参数
    parameters:
      - name: param
        in: body
        description: 实体名称，用于进行查询的参数，示例输入“采煤机”
        required: true
        schema:
          type: object
          properties:
            entityName:
              type: string
    responses:
      200:
        description: 查询成功
    """
    entityName = request.json.get('entityName')
    searchResult = neo4jDB.mathEntityListByKeyWord(entityName)
    res = []
    for i, item in enumerate(searchResult):
        if i >= 10:
            break
        res.append({
            "value": item['entity1.name'],
            "info": "暂时没有"
        })
    return resDTO(data=res)


# 根据一个实体名称进行查询
@graphBlueprint.post("/selectByEntity")
def selectGraphByEntity():
    """
    实体查询接口，双向查询
    ---
    tags:
      - 图查询接口
    description:
        图查询接口，json格式参数
    parameters:
      - name: param
        in: body
        description: 实体名称，用于进行查询的参数，示例输入“采煤机”，性能限制，level限制为1、2、3
        required: true
        schema:
          type: object
          properties:
            entityName:
              type: string
            level:
              type: integer
    responses:
      200:
        description: 查询成功
    """
    entityName = request.json.get('entityName')
    level = request.json.get('level')
    if level == 0 or level > 3:
        return errDTO(msg="级数异常，4>level>0")
    result = neo4jDB.search_all(entity1=entityName, numberOfLevels=level)
    nodes, edges = searchResPathToGraph(result)
    result = {
        'nodes': nodes,
        'edges': edges
    }
    return resDTO(data=result)


# 根据头实体和关系查询尾实体
@graphBlueprint.post("/selectByEntityAndRel")
def selectByEntityAndRel():
    entityName = request.json.get('entityName')
    relation = request.json.get('relation')
    result = neo4jDB.search_all(entity1=entityName, relation=relation)
    result = searchResToDict(result)
    result = {
        'result': result
    }
    return resDTO(data=result)


# 根据头实体和尾实体查询他们的最短路径
@graphBlueprint.post("/selectShortRoute")
def selectShortRoute():
    source_name = request.json.get('source_name')
    target_name = request.json.get('target_name')
    result = neo4jDB.search_all(entity1=source_name, entity2=target_name)
    result = searchResToDict(result)
    result = {
        'result': result
    }
    return resDTO(data=result)

@graphBlueprint.route("/graphDataByEventId", methods=["GET"])
def graphDataByEventId():
    event_id = request.args.get('eventId')
    if not event_id:
        return jsonify({"code": 400, "message": "eventId不能为空"}), 400

    result = neo4jDB.matchAllIncomingPathsToEntity(entity2=event_id)
    if not result:
        return jsonify({"code": 200, "data": {"rootId": event_id, "nodes": [], "lines": []}})

    nodes_set = set()
    lines_set = set()

    # 用一个set保存当前“允许”作为入边终点的节点，初始是目标节点event_id
    allowed_targets = {event_id}

    for record in result:
        path = record.get('path')
        if not path:
            continue

        # 路径的节点列表和关系列表
        nodes = path.nodes
        rels = path.relationships

        # 遍历路径中所有关系，收集方向正确且关系的终点在allowed_targets中的边
        for rel in rels:
            from_id = rel.start_node['name']
            to_id = rel.end_node['name']
            if to_id in allowed_targets:
                weight_val = rel.get('weight', 1)
                # 加入节点（起点和终点）
                nodes_set.add((from_id, from_id))
                nodes_set.add((to_id, to_id))
                # 加入边
                lines_set.add((from_id, to_id, str(weight_val)))

                # 新增入边的起点，作为下一轮的allowed_targets，保证多级递归入边都能被收集
                allowed_targets.add(from_id)

    nodes = []
    for nid, text in nodes_set:
        if nid != event_id:
            nodes.append({"id": nid, "text": text})
    lines = [{"from": f, "to": t, "text": w} for f, t, w in lines_set]

    return jsonify({
        "code": 200,
        "data": {
            "rootId": event_id,
            "nodes": nodes,
            "lines": lines
        }
    })

@graphBlueprint.route("/insertTestData", methods=["POST"])
def insertTestData():
    """
    插入固定测试数据接口：10个节点、20条关系，带方向和权重
    用于测试多层图关系查询逻辑
    """
    try:
        # 10 个测试节点：sensor_A ~ sensor_J
        test_nodes = [{"id": f"sensor_{chr(65+i)}", "text": f"Sensor {chr(65+i)}"} for i in range(10)]

        # 20 条测试关系（有向、部分重复连接形成网络结构）
        test_lines = [
            {"from": "sensor_A", "to": "sensor_B", "text": "0.91"},
            {"from": "sensor_A", "to": "sensor_C", "text": "0.87"},
            {"from": "sensor_B", "to": "sensor_D", "text": "0.76"},
            {"from": "sensor_C", "to": "sensor_D", "text": "0.88"},
            {"from": "sensor_C", "to": "sensor_E", "text": "0.82"},
            {"from": "sensor_D", "to": "sensor_F", "text": "0.93"},
            {"from": "sensor_E", "to": "sensor_F", "text": "0.84"},
            {"from": "sensor_F", "to": "sensor_G", "text": "0.79"},
            {"from": "sensor_G", "to": "sensor_H", "text": "0.85"},
            {"from": "sensor_H", "to": "sensor_I", "text": "0.77"},
            {"from": "sensor_I", "to": "sensor_J", "text": "0.89"},
            {"from": "sensor_B", "to": "sensor_E", "text": "0.68"},
            {"from": "sensor_E", "to": "sensor_H", "text": "0.72"},
            {"from": "sensor_A", "to": "sensor_F", "text": "0.83"},
            {"from": "sensor_B", "to": "sensor_G", "text": "0.66"},
            {"from": "sensor_C", "to": "sensor_H", "text": "0.74"},
            {"from": "sensor_D", "to": "sensor_I", "text": "0.81"},
            {"from": "sensor_E", "to": "sensor_J", "text": "0.73"},
            {"from": "sensor_F", "to": "sensor_I", "text": "0.92"},
            {"from": "sensor_G", "to": "sensor_J", "text": "0.69"}
        ]

        # 插入节点
        for node in test_nodes:
            cql_node = """
                MERGE (n:Entity {name: $name})
                SET n.text = $text
            """
            neo4jDB.graph.run(cql_node, name=node["id"], text=node["text"])

        # 插入关系（方向：from → to）
        for line in test_lines:
            cql_rel = """
                MATCH (a:Entity {name: $from_id}), (b:Entity {name: $to_id})
                MERGE (a)-[r:RELATION]->(b)
                SET r.weight = toFloat($weight)
            """
            neo4jDB.graph.run(cql_rel, from_id=line["from"], to_id=line["to"], weight=line["text"])

    except Exception as e:
        return jsonify({"code": 500, "message": f"插入失败: {e}"}), 500

    return jsonify({"code": 200, "message": "测试数据插入成功"})
